package vm

import (
	"go-forth/pkg/forth"
	"go-forth/pkg/forth/standard"
	"io"
	"io/fs"
	"os"
)

type VM struct {
	input     forth.InputSourceSpec
	output    io.Writer
	syscall   *GoSyscall
	dataSpace *DataSpace

	compiling int

	inputs   *Stack
	values   *Stack
	returnS  *Stack
	controlS *Stack
	words    *Table
	openWord Word

	delayed       forth.ExecutionOnToken
	nextAddrToRun int
}

func (vm *VM) Push(token forth.Token) {
	vm.values.Push(token)
}

func (vm *VM) Pop() forth.Token {
	return vm.values.Pop()
}

func (vm *VM) Clear() {
	vm.values.Clear()
}

func (vm *VM) ReturnStack() forth.Stack {
	return vm.returnS
}

func (vm *VM) ControlStack() forth.Stack {
	return vm.controlS
}

func (vm *VM) DataSpace() forth.DataSpace {
	return vm.dataSpace
}

func (vm *VM) Input() forth.InputSourceSpec {
	return vm.input
}

func (vm *VM) Output() io.Writer {
	return vm.output
}

func (vm *VM) Register(s string, tokens ...func(forth.StackVM)) {
	vm.openWord = vm.words.Register(s, false, tokens...)
}

func (vm *VM) Append(tokens ...forth.Token) {
	vm.openWord.Append(tokens...)
}

func (vm *VM) Find(s string) (forth.ExecutionToken, bool, bool) {
	return vm.words.Find(s)
}

func (vm *VM) Delay(f forth.ExecutionOnToken) {
	vm.delayed = f
}

func (vm *VM) Goto(addr int) {
	vm.nextAddrToRun = addr
}

func (vm *VM) Execute(from, to int) {
	for i := from; i < to; i++ {
		token := vm.DataSpace().Fetch(i)
		if token == nil {
			panic("nil token")
		}
		if vm.delayed != nil {
			f := vm.delayed
			vm.delayed = nil
			f(token)
			continue
		}
		forth.TryExecute(token, vm)
		if next := vm.nextAddrToRun; next > 0 {
			if next < from || next > to {
				panic("invalid address to jump")
			}
			i = vm.nextAddrToRun - 1
			vm.nextAddrToRun = 0
		}
	}
}

func (vm *VM) Abort() {
	xt, _, _ := vm.Find("ABORT")
	xt.RunOn(vm)
}

func (vm *VM) SetInput(in forth.Token) {
	vm.inputs.Push(vm.input)
	if s, ok := in.(string); ok {
		vm.input = newStringInput(s)
	} else if r, ok := in.(io.Reader); ok {
		vm.input = newGenericInput(r)
	} else {
		panic("unsupported")
	}
}

func (vm *VM) ResetInput() {
	vm.input = vm.inputs.Pop().(forth.InputSourceSpec)
}

func (vm *VM) Syscall() forth.Syscall {
	return vm.syscall
}

func New(r io.Reader, w io.Writer) *VM {
	data := &DataSpace{
		data: []forth.Token{
			0, // STATE
		},
		reserved: 1,
	}

	words := NewTable(data)
	// reserved states
	words.Register("STATE", false, func(vm forth.StackVM) {
		vm.Push(0)
	})
	// core
	words.Register("WORD", false, standard.Word)
	words.Register("FIND", false, standard.Find)
	words.Register("COMPILE,", false, standard.CompileComma)
	words.Register("INTERPRET", false, standard.Interpret)
	words.Register("QUIT", false, standard.Quit)
	words.Register("EMIT", false, standard.Emit)
	words.Register("BYE", false, standard.Bye)
	words.Register(":", false, standard.Colon)
	words.Register(";", true,
		// LIT
		func(vm forth.StackVM) {
			vm.Delay(func(token forth.Token) {
				vm.Append(token)
			})
		},
		standard.Exit,
		standard.Lbrac,
		standard.Exit,
	)
	words.Register("[", true, standard.Lbrac)
	words.Register("]", false, standard.Rbrac)
	words.Register("CREATE", false, standard.Create)
	words.Register(">R", false, standard.ToR)
	words.Register("R>", false, standard.FromR)
	words.Register("EXECUTE", false, standard.Execute)
	words.Register("'", false, standard.Tick)
	words.Register(",", false, standard.Comma)
	words.Register("[']", true, standard.BracketTick)
	words.Register("LITERAL", true, standard.Literal)
	words.Register("POSTPONE", true, standard.Postpone)
	words.Register("HERE", false, standard.Here)
	words.Register("@", false, standard.Fetch)
	words.Register("!", false, standard.Store)
	words.Register("ABORT\"", true, standard.AbortQuote)
	words.Register("ABS", false, standard.Abs)
	words.Register("(", true, standard.Paren)
	words.Register("+", false, standard.Plus)
	words.Register("-", false, standard.Minus)
	words.Register("*", false, standard.Multiply)
	words.Register("/", false, standard.Divide)
	words.Register("2DUP", false, standard.TwoDupe)
	words.Register("ALLOT", false, standard.Allot)
	words.Register("CELL+", false, standard.CellPlus)
	words.Register("CELLS", false, standard.NoOp)
	words.Register("TRUE", false, standard.True)
	words.Register("FALSE", false, standard.False)
	words.Register("AND", false, standard.And)
	words.Register("OR", false, standard.Or)
	words.Register("XOR", false, standard.Xor)
	words.Register("=", false, standard.Equals)
	words.Register("<", false, standard.LessThan)
	words.Register(">", false, standard.GreaterThan)
	words.Register("<>", false, standard.NotEquals)
	words.Register(">BODY", false, standard.ToBody)
	words.Register("CHARS", false, standard.NoOp)
	words.Register("ACCEPT", false, standard.Accept)
	words.Register("<#", false, standard.LessNumberSign)
	words.Register("#", false, standard.NumberSign)
	words.Register("#>", false, standard.NumberSignGreater)
	words.Register("BEGIN", true, standard.Begin)
	words.Register("UNTIL", true, standard.Until)
	words.Register("DUP", false, standard.Dupe)
	words.Register("\\", true, standard.Backslash)
	words.Register("WHILE", true, standard.While)
	words.Register("REPEAT", true, standard.Repeat)
	words.Register("DROP", false, standard.Drop)
	words.Register(".\"", true, standard.DotQuote)
	words.Register("S\"", true, standard.SQuote)
	words.Register("TYPE", false, standard.Type)
	words.Register("ROT", false, standard.Rote)
	words.Register("#S", false, standard.NumberSignS)
	words.Register("SIGN", false, standard.Sign)
	words.Register("IF", true, standard.If)
	words.Register("ELSE", true, standard.Else)
	words.Register("THEN", true, standard.Then)
	words.Register("SWAP", false, standard.Swap)
	words.Register("EXIT", false, standard.Exit)
	words.Register("COUNT", false, standard.Count)
	words.Register("?DUP", false, standard.QuestionDupe)
	words.Register("KEY", false, standard.Key)
	words.Register("AGAIN", true, standard.Again)
	// file
	words.Register("S\"", true, standard.SQuote2)
	words.Register("OPEN-FILE", false, standard.OpenFile)
	words.Register("INCLUDE-FILE", false, standard.IncludeFile)
	words.Register("OPEN-DIR", false, standard.OpenDir)
	words.Register("READ-DIR", false, standard.ReadDir)
	words.Register("CLOSE-DIR", false, standard.CloseDir)

	// string
	words.Register("SEARCH", false, standard.Search)
	words.Register("COMPARE", false, standard.Compare)

	ret := &VM{
		input:  newGenericInput(r),
		output: w,
		syscall: &GoSyscall{
			filesystem: os.DirFS("."),
			dataSpace:  data,
		},
		dataSpace: data,

		compiling: 0,

		inputs:   NewStack(),
		values:   NewStack(),
		returnS:  NewStack(),
		controlS: NewStack(),
		words:    words,
	}

	// Implementation dependant words
	words.Register("DSP@", false, func(vm forth.StackVM) {
		vm.Push(-len(ret.values.tokens))
	})
	words.Register("ABORT", false, func(vm forth.StackVM) {
		ret.values.Clear()
		ret.returnS.Clear()
		panic("QUIT")
	})
	words.Register("IMMEDIATE", false, func(vm forth.StackVM) {
		ret.openWord.MarkImmediate()
	})
	words.Register("DEPTH", false, func(vm forth.StackVM) {
		vm.Push(len(ret.values.tokens))
	})
	words.Register("REFILL", false, func(vm forth.StackVM) {
		vm.Push(vm.Input().Refill())
	})
	words.Register("PAUSE", false, func(vm forth.StackVM) {
		x := vm.Pop()
		vm.Push(x)
	})

	// Words implemented in Forth
	ret.evaluate(` \ Core word set
\ C=1
: BL 32 ;
: 2DROP DROP DROP ;

\ C=2
: SPACE BL EMIT ;
: CR 10 EMIT ;
: 0= 0 = ;
: 0> 0 > ;
: 1+ 1 + ;
: 1- 1 - ;

\ C=4 
: DEPTH 0 DSP@ - 1- ;

\ C=9
: . DUP ABS 0 <# #S ROT SIGN #> TYPE SPACE ;

: CONSTANT >R : R> POSTPONE LITERAL POSTPONE ; ;
0 CONSTANT R/O

: VARIABLE HERE 1 ALLOT CONSTANT ;
VARIABLE BASE 10 BASE !
VARIABLE _pictured_numeric_output_string_buffer_offset

: .(	( -- )
	STATE @ IF	( compiling? )
		POSTPONE S"
		' TYPE ,
	ELSE
		( In immediate mode, just read characters and print them until ending paren )
		BEGIN
			KEY
			DUP 41 = IF
				DROP	( drop the ending paren )
				EXIT	( and exit the loop )
			THEN EMIT
		AGAIN
	THEN
; IMMEDIATE

\ File-Access word set
: INCLUDED R/O OPEN-FILE ABORT" error opening file" INCLUDE-FILE ;
: INCLUDE WORD COUNT INCLUDED ;

\ Programming-Tools word set
: [THEN] ( -- ) ; IMMEDIATE
: [ELSE] ( -- )
    1 BEGIN                                          \ level
       BEGIN WORD COUNT DUP WHILE                  \ level adr len
         2DUP S" [IF]" COMPARE 0= IF                  \ level adr len
             2DROP 1+                                 \ level'
          ELSE                                        \ level adr len
            2DUP S" [ELSE]" COMPARE 0= IF             \ level adr len
                2DROP 1- DUP IF 1+ THEN               \ level'
            ELSE                                      \ level adr len
                S" [THEN]" COMPARE 0= IF              \ level
                   1-                                 \ level'
               THEN
             THEN
          THEN ?DUP 0= IF EXIT THEN                   \ level'
       REPEAT 2DROP                                   \ level
    REFILL 0= UNTIL                                   \ level
	DROP
; IMMEDIATE
: [IF] ( flag -- )
   0= IF POSTPONE [ELSE] THEN
; IMMEDIATE
`)

	return ret
}

func (vm *VM) Run() {
	for vm.doRun() {
	}
}

func (vm *VM) doRun() (retry bool) {
	defer func() {
		r := recover()
		if r == "QUIT" {
			retry = true // reset and rerun
		} else if r == "EOF" {
			retry = false
		} else {
			panic(r)
		}
	}()
	xt, _, _ := vm.Find("QUIT")
	xt.RunOn(vm)
	return false
}

func (vm *VM) evaluate(s string) {
	vm.Push(s)
	standard.Evaluate(vm)
}

func (vm *VM) registerHandle(f fs.File) int {
	here := vm.dataSpace.Reserved()
	vm.dataSpace.Reserve(2)
	vm.dataSpace.Store(here, f)
	vm.dataSpace.Store(here+1, 0) // position
	return here
}

var _ forth.StackVM = &VM{}
